package r25.tools;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.deser.std.UntypedObjectDeserializer;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.ser.std.SerializerBase;
import org.codehaus.jackson.type.TypeReference;
import org.slf4j.Logger;

import r25.tools.DateHelper.DF;

/**
 * <p>
 * JsonHelper class.
 * </p>
 * 
 * @author u211354
 * @version $Id: $
 */
public class JsonHelper {
	private static final Logger logger = LoggerHelper.getLogger();
	/** Constant <code>DATE_MARKER="$date://"</code> */
	public static final String DATE_MARKER = "$date://";

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param file
	 *            a {@link java.io.File} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(File file) throws JsonParseException,
			IOException {
		return loadJson(file, new TypeReference<T>() {
		});
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param file
	 *            a {@link java.io.File} object.
	 * @param tr
	 *            a {@link org.codehaus.jackson.type.TypeReference} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(File file, TypeReference<T> tr)
			throws JsonParseException, IOException {
		InputStream in = null;
		try {
			in = new FileInputStream(file);
			return loadJson(in, tr);
		} finally {
			if (in != null)
				in.close();
		}
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param str
	 *            a {@link java.lang.String} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(String str) throws JsonParseException,
			IOException {
		return loadJson(str, new TypeReference<T>() {
		});
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param df
	 *            a {@link java.text.DateFormat} object.
	 * @param str
	 *            a {@link java.lang.String} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(DateFormat df, String str)
			throws JsonParseException, IOException {
		return loadJson(df, str, new TypeReference<T>() {
		});
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param str
	 *            a {@link java.lang.String} object.
	 * @param tr
	 *            a {@link org.codehaus.jackson.type.TypeReference} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(String str, TypeReference<T> tr)
			throws JsonParseException, IOException {
		return loadJson(new ByteArrayInputStream(str.getBytes()), tr);
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param df
	 *            a {@link java.text.DateFormat} object.
	 * @param str
	 *            a {@link java.lang.String} object.
	 * @param tr
	 *            a {@link org.codehaus.jackson.type.TypeReference} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(DateFormat df, String str,
			TypeReference<T> tr) throws JsonParseException, IOException {
		return loadJson(df, new ByteArrayInputStream(str.getBytes()), tr);
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param in
	 *            a {@link java.io.InputStream} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(InputStream in)
			throws JsonParseException, IOException {
		return loadJson(in, new TypeReference<T>() {
		});
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param in
	 *            a {@link java.io.InputStream} object.
	 * @param tr
	 *            a {@link org.codehaus.jackson.type.TypeReference} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(InputStream in, TypeReference<T> tr)
			throws JsonParseException, IOException {
		return loadJson(new InputStreamReader(in), tr);
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param df
	 *            a {@link java.text.DateFormat} object.
	 * @param in
	 *            a {@link java.io.InputStream} object.
	 * @param tr
	 *            a {@link org.codehaus.jackson.type.TypeReference} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(DateFormat df, InputStream in,
			TypeReference<T> tr) throws JsonParseException, IOException {
		return loadJson(df, new InputStreamReader(in), tr);
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param in
	 *            a {@link java.io.Reader} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(Reader in) throws JsonParseException,
			IOException {
		return loadJson(in, new TypeReference<T>() {
		});
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param in
	 *            a {@link java.io.Reader} object.
	 * @param tr
	 *            a {@link org.codehaus.jackson.type.TypeReference} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(Reader in, TypeReference<T> tr)
			throws JsonParseException, IOException {
		JsonFactory jf = new JsonFactory();
		jf.enable(JsonParser.Feature.ALLOW_COMMENTS);
		JsonParser jp = jf.createJsonParser(in);

		ObjectMapper jm = new ObjectMapper();
		SimpleModule dateModule = new SimpleModule("DModule", new Version(1, 0,
				0, null));
		dateModule.addDeserializer(Object.class, new DHDateDeserializer());
		jm.registerModule(dateModule);

		return jm.readValue(jp, tr);
	}

	/**
	 * <p>
	 * loadJson
	 * </p>
	 * 
	 * @param df
	 *            a {@link java.text.DateFormat} object.
	 * @param in
	 *            a {@link java.io.Reader} object.
	 * @param tr
	 *            a {@link org.codehaus.jackson.type.TypeReference} object.
	 * @param <T>
	 *            a T object.
	 * @return a T object.
	 * @throws org.codehaus.jackson.JsonParseException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final <T> T loadJson(DateFormat df, Reader in,
			TypeReference<T> tr) throws JsonParseException, IOException {
		JsonFactory jf = new JsonFactory();
		jf.enable(JsonParser.Feature.ALLOW_COMMENTS);
		JsonParser jp = jf.createJsonParser(in);

		ObjectMapper jm = new ObjectMapper();
		jm.setDateFormat(df);
		return jm.readValue(jp, tr);
	}

	/**
	 * <p>
	 * saveJson
	 * </p>
	 * 
	 * @param file
	 *            a {@link java.io.File} object.
	 * @param val
	 *            a {@link java.lang.Object} object.
	 * @throws org.codehaus.jackson.JsonGenerationException
	 *             if any.
	 * @throws org.codehaus.jackson.map.JsonMappingException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final void saveJson(File file, Object val)
			throws JsonGenerationException, JsonMappingException, IOException {
		OutputStream out = null;
		try {
			out = new FileOutputStream(file);
			saveJson(out, val);
		} finally {
			if (out != null)
				out.close();
		}
	}

	/**
	 * <p>
	 * saveJson
	 * </p>
	 * 
	 * @param out
	 *            a {@link java.io.OutputStream} object.
	 * @param val
	 *            a {@link java.lang.Object} object.
	 * @throws org.codehaus.jackson.JsonGenerationException
	 *             if any.
	 * @throws org.codehaus.jackson.map.JsonMappingException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final void saveJson(OutputStream out, Object val)
			throws JsonGenerationException, JsonMappingException, IOException {
		saveJson(new OutputStreamWriter(out), val);
	}

	/**
	 * <p>
	 * saveJson
	 * </p>
	 * 
	 * @param out
	 *            a {@link java.io.Writer} object.
	 * @param val
	 *            a {@link java.lang.Object} object.
	 * @throws org.codehaus.jackson.JsonGenerationException
	 *             if any.
	 * @throws org.codehaus.jackson.map.JsonMappingException
	 *             if any.
	 * @throws java.io.IOException
	 *             if any.
	 */
	public static final void saveJson(Writer out, Object val)
			throws JsonGenerationException, JsonMappingException, IOException {
		ObjectMapper jm = new ObjectMapper();
		jm.writeValue(out, val);
	}

	/**
	 * <p>
	 * toJson
	 * </p>
	 * 
	 * @param val
	 *            a {@link java.lang.Object} object.
	 * @return a {@link java.lang.String} object.
	 * @throws java.lang.Exception
	 *             if any.
	 */
	public static final String toJson(Object val) throws Exception {
		ObjectMapper jm = new ObjectMapper();
		SimpleModule dateModule = new SimpleModule("SModule", new Version(1, 0,
				0, null));
		dateModule.addSerializer(new DHDateSerializer(Date.class));
		jm.registerModule(dateModule);

		return jm.writeValueAsString(val);
	}

	/**
	 * <p>
	 * toJson
	 * </p>
	 * 
	 * @param df
	 *            a {@link java.text.DateFormat} object.
	 * @param val
	 *            a {@link java.lang.Object} object.
	 * @return a {@link java.lang.String} object.
	 * @throws java.lang.Exception
	 *             if any.
	 */
	public static final String toJson(DateFormat df, Object val)
			throws Exception {
		ObjectMapper jm = new ObjectMapper();
		jm.setDateFormat(df);

		return jm.writeValueAsString(val);
	}

	private static class DHDateDeserializer extends UntypedObjectDeserializer {
		private static final Logger logger = LoggerHelper.getLogger();

		protected DHDateDeserializer() {
			super();
		}

		@Override
		public Object deserialize(JsonParser jp, DeserializationContext ctxt)
				throws IOException, JsonProcessingException {
			if (jp.getCurrentToken() == JsonToken.VALUE_STRING) {
				String text = jp.getText();
				if (text.startsWith(DATE_MARKER)) {
					logger.trace("{}", text);
					try {
						return DateHelper.getDateFormat(DF.ISO_8601).parse(
								text.substring(8));
					} catch (ParseException e) {
						throw new IOException("Unable to deserialize " + text,
								e);
					}
				}
				return jp.getText();
			}
			return super.deserialize(jp, ctxt);
		}
	}

	private static class DHDateSerializer extends SerializerBase<Date> {

		protected DHDateSerializer(Class<Date> t) {
			super(t);
		}

		@Override
		public void serialize(Date value, JsonGenerator jgen,
				SerializerProvider provider) throws IOException,
				JsonProcessingException {
			String text = DATE_MARKER
					+ DateHelper.getDateFormat(DF.ISO_8601).format(value);

			logger.trace("{}", text);
			jgen.writeString(text);
		}
	}
}
